﻿/**
 * Notes: 请求辅助模块 - 请求合并、队列管理、缓存优化和重试
 * Date: 2023-12-25
 */

const cloudHelper = require('./cloud_helper.js');
const cacheHelper = require('./cache_helper.js');
const helper = require('./helper.js');
const timeHelper = require('./time_helper.js');
const setting = require('../config/projects.js');
const constants = require('../constants/constants.js');

// 缓存配置
const CACHE_PREFIX = 'REQ_CACHE_';
const DEFAULT_CACHE_TIME = 30 * 60; // 默认缓存30分钟

// 请求队列
const requestQueue = [];
const MAX_CONCURRENT_REQUESTS = 5; // 最大并发请求数
let runningRequests = 0;

// 请求合并映射 - 跟踪正在处理的请求
const pendingRequests = {};

// 请求重试配置
const MAX_RETRY_COUNT = 3;
const RETRY_DELAY = 1000; // 重试间隔，毫秒

// 批量请求功能状态
let BATCH_REQUEST_ENABLED = true;

// 缓存组映射
const CACHE_GROUP_MAP = {
    // 首页和工作台
    home: cacheHelper.CACHE_GROUP.USER,
    user: cacheHelper.CACHE_GROUP.USER,
    
    // 列表数据
    list: cacheHelper.CACHE_GROUP.LIST,
    search: cacheHelper.CACHE_GROUP.LIST,
    
    // 详情数据
    detail: cacheHelper.CACHE_GROUP.DETAIL,
    info: cacheHelper.CACHE_GROUP.DETAIL,
    
    // 配置类数据
    config: cacheHelper.CACHE_GROUP.CONFIG,
    setting: cacheHelper.CACHE_GROUP.CONFIG,
    
    // 系统缓存
    system: cacheHelper.CACHE_GROUP.SYSTEM,
    
    // 默认临时缓存
    default: cacheHelper.CACHE_GROUP.TEMP
};

// 缓存优先级配置
const CACHE_PRIORITY_CONFIG = {
    // 重要且不经常变化的数据，高优先级
    high: [
        'user/profile',
        'home/nav',
        'setting/all',
    ],
    
    // 重要但会变化的数据，中优先级
    medium: [
        'home/work_data',
        'home/statistics',
        'list/project'
    ],
    
    // 不太重要的数据，低优先级
    low: [
        'home/dynamic_list',
        'search/history'
    ]
};

/**
 * 生成请求缓存键
 * @param {String} route 请求路径
 * @param {Object} params 请求参数
 */
function getCacheKey(route, params = {}) {
    // 生成参数字符串
    let paramsStr = '';
    if (Object.keys(params).length > 0) {
        const sortedParams = Object.keys(params).sort().map(key => {
            return `${key}=${JSON.stringify(params[key])}`;
        });
        paramsStr = sortedParams.join('&');
    }
    
    // 生成缓存键名
    const key = `API_${route}_${paramsStr}`;
    return key;
}

/**
 * 获取缓存组
 * @param {String} route 请求路径
 */
function getCacheGroup(route) {
    if (!route) return CACHE_GROUP_MAP.default;
    
    // 尝试根据路径前缀匹配缓存组
    const routeParts = route.split('/');
    if (routeParts.length > 0) {
        const prefix = routeParts[0].toLowerCase();
        if (CACHE_GROUP_MAP[prefix]) {
            return CACHE_GROUP_MAP[prefix];
        }
    }
    
    return CACHE_GROUP_MAP.default;
}

/**
 * 获取缓存优先级
 * @param {String} route 请求路径
 */
function getCachePriority(route) {
    if (!route) return cacheHelper.CACHE_PRIORITY.NORMAL;
    
    // 检查是否在高优先级列表中
    if (CACHE_PRIORITY_CONFIG.high.some(pattern => route.includes(pattern))) {
        return cacheHelper.CACHE_PRIORITY.HIGH;
    }
    
    // 检查是否在中优先级列表中
    if (CACHE_PRIORITY_CONFIG.medium.some(pattern => route.includes(pattern))) {
        return cacheHelper.CACHE_PRIORITY.NORMAL;
    }
    
    // 默认为低优先级
    return cacheHelper.CACHE_PRIORITY.LOW;
}

/**
 * 获取缓存数据
 * @param {String} route 请求路径
 * @param {Object} params 请求参数 
 * @param {Number} cacheTime 缓存时间（秒）
 */
function getCache(route, params = {}, cacheTime = DEFAULT_CACHE_TIME) {
    const cacheKey = getCacheKey(route, params);
    return cacheHelper.get(cacheKey);
}

/**
 * 设置缓存数据
 * @param {String} route 请求路径
 * @param {Object} params 请求参数
 * @param {Any} data 要缓存的数据
 * @param {Number} cacheTime 缓存时间（秒）
 * @param {String} group 缓存分组
 * @param {Number} priority 缓存优先级
 */
function setCache(route, params = {}, data, cacheTime = DEFAULT_CACHE_TIME, group = null, priority = null) {
    const cacheKey = getCacheKey(route, params);
    // 如果没有指定组和优先级，则自动根据路由确定
    const cacheGroup = group || getCacheGroup(route);
    const cachePriority = priority || getCachePriority(route);
    cacheHelper.set(cacheKey, data, cacheTime, cacheGroup, cachePriority);
}

/**
 * 清除指定前缀的所有缓存
 * @param {String} prefixKey 缓存键前缀
 */
function clearCacheByPrefix(prefixKey) {
    try {
        const allKeys = wx.getStorageInfoSync().keys;
        const keys = allKeys.filter(key => key.startsWith(CACHE_PREFIX + prefixKey));
        
        for (let key of keys) {
            wx.removeStorageSync(key);
            wx.removeStorageSync(key + '_deadtime');
        }
        
        return keys.length;
    } catch (err) {
        console.error('清除缓存失败', err);
        return 0;
    }
}

/**
 * 清除所有请求缓存
 */
function clearAllCache() {
    try {
        const allKeys = wx.getStorageInfoSync().keys;
        const keys = allKeys.filter(key => key.startsWith(CACHE_PREFIX));
        
        for (let key of keys) {
            wx.removeStorageSync(key);
            wx.removeStorageSync(key + '_deadtime');
        }
        
        return keys.length;
    } catch (err) {
        console.error('清除所有缓存失败', err);
        return 0;
    }
}

/**
 * 处理队列中的下一个请求
 */
function processNextRequest() {
    if (requestQueue.length === 0 || runningRequests >= MAX_CONCURRENT_REQUESTS) {
        return;
    }
    
    runningRequests++;
    const request = requestQueue.shift();
    
    executeRequest(request)
        .then(result => {
            request.resolve(result);
        })
        .catch(error => {
            request.reject(error);
        })
        .finally(() => {
            runningRequests--;
            processNextRequest();
        });
}

/**
 * 执行请求
 */
async function executeRequest({ route, params, options, retryCount = 0 }) {
    try {
        return await cloudHelper.callCloudData(route, params, options);
    } catch (error) {
        // 处理特定错误
        if (error && error.type === 'timeout') {
            console.warn(`[REQUEST] 请求超时: ${route}`, error);
            
            // 网络超时，可以进行重试
            if (retryCount < MAX_RETRY_COUNT) {
                console.log(`[REQUEST] 准备第${retryCount + 1}次重试: ${route}`);
                
                // 指数退避策略延迟
                const delay = RETRY_DELAY * Math.pow(2, retryCount);
                
                return new Promise((resolve, reject) => {
                    setTimeout(async () => {
                        try {
                            const result = await executeRequest({ 
                                route, 
                                params, 
                                options: { ...options, hint: false }, // 重试时不显示提示
                                retryCount: retryCount + 1 
                            });
                            resolve(result);
                        } catch (retryError) {
                            reject(retryError);
                        }
                    }, delay);
                });
            }
        } else if (error && error.type === 'network') {
            console.warn(`[REQUEST] 网络错误: ${route}`, error);
            
            // 网络错误，可以进行重试
            if (retryCount < MAX_RETRY_COUNT) {
                console.log(`[REQUEST] 网络错误，准备第${retryCount + 1}次重试: ${route}`);
                
                // 指数退避策略延迟
                const delay = RETRY_DELAY * Math.pow(2, retryCount);
                
                return new Promise((resolve, reject) => {
                    setTimeout(async () => {
                        try {
                            const result = await executeRequest({ 
                                route, 
                                params, 
                                options: { ...options, hint: false }, // 重试时不显示提示
                                retryCount: retryCount + 1 
                            });
                            resolve(result);
                        } catch (retryError) {
                            reject(retryError);
                        }
                    }, delay);
                });
            }
        }
        
        // 其他错误或超过重试次数，抛出原始错误
        throw error;
    }
}

/**
 * 禁用批量请求功能
 * 当批量请求多次失败后，系统会自动禁用此功能，改用单独请求
 */
function disableBatchRequest() {
    console.warn('批量请求功能已被禁用，将使用单独请求');
    BATCH_REQUEST_ENABLED = false;
}

/**
 * 智能请求函数 - 支持缓存、合并、队列与重试
 * @param {String} route 请求路径
 * @param {Object} params 请求参数
 * @param {Object} options 请求选项
 * @param {Number} cacheTime 缓存时间（秒）
 * @param {Boolean} forceRefresh 是否强制刷新
 */
function requestData(route, params = {}, options = {}, cacheTime = DEFAULT_CACHE_TIME, forceRefresh = false) {
    if (!route) {
        console.error('[REQUEST] 路由不能为空');
        return Promise.reject(new Error('路由不能为空'));
    }
    
    // 检查是否需要使用缓存
    const useCache = !forceRefresh && shouldUseCache(route, options);
    
    // 生成请求键
    const requestKey = getCacheKey(route, params);
    
    // 检查是否有正在处理的相同请求
    if (pendingRequests[requestKey]) {
        console.log(`[REQUEST] 合并重复请求: ${route}`);
        return pendingRequests[requestKey];
    }
    
    // 如果使用缓存且有缓存数据，直接返回
    if (useCache) {
        const cachedData = getCache(route, params);
        if (cachedData) {
            console.log(`[REQUEST] 使用缓存数据: ${route}`);
            return Promise.resolve(cachedData);
        }
    }
    
    // 创建请求Promise
    const requestPromise = new Promise((resolve, reject) => {
        // 将请求添加到队列
        requestQueue.push({
            route,
            params,
            options,
            resolve: (result) => {
                // 请求成功，清除pendingRequests中的记录
                delete pendingRequests[requestKey];
                
                // 如果需要缓存，则缓存结果
                if (useCache && result) {
                    const calculatedCacheTime = getCacheTime(route, cacheTime);
                    const cacheGroup = getCacheGroup(route);
                    const cachePriority = getCachePriority(route);
                    setCache(route, params, result, calculatedCacheTime, cacheGroup, cachePriority);
                }
                
                resolve(result);
            },
            reject: (error) => {
                // 请求失败，清除pendingRequests中的记录
                delete pendingRequests[requestKey];
                
                // 尝试返回过期的缓存数据作为降级策略
                if (options.useCacheOnError !== false) {
                    const expiredCache = cacheHelper.getExpired(requestKey);
                    if (expiredCache) {
                        console.warn(`[REQUEST] 请求失败，使用过期缓存: ${route}`, error);
                        
                        // 标记数据来源于过期缓存
                        if (typeof expiredCache === 'object') {
                            expiredCache._fromExpiredCache = true;
                            expiredCache._errorMessage = error.message || '请求失败';
                        }
                        
                        resolve(expiredCache);
                        return;
                    }
                }
                
                reject(error);
            }
        });
        
        // 处理队列中的下一个请求
        processNextRequest();
    });
    
    // 记录到pendingRequests
    pendingRequests[requestKey] = requestPromise;
    
    return requestPromise;
}

/**
 * 批量请求 - 将多个请求合并为一个
 * @param {Array} requests 请求配置数组，每个元素包含 route, params
 * @param {Object} options 请求选项
 */
async function batchRequest(requests, options = {}) {
    if (!requests || requests.length === 0) return [];
    
    // 如果批量请求功能已被禁用，则使用单独请求模式
    if (!BATCH_REQUEST_ENABLED) {
        console.log('使用单独请求模式替代批量请求');
        return individualRequests(requests, options);
    }
    
    // 1. 生成组合请求的key和参数
    const batchKey = 'batch_request';
    const batchParams = {
        requests: requests.map(r => ({
            route: r.route,
            params: r.params || {}
        }))
    };
    
    try {
        // 2. 调用批量请求云函数
        const result = await requestData('util/batch_request', batchParams, options, 0, true);
        
        // 检查返回数据的格式
        console.log('批量请求响应数据:', result);
        
        // 适配后端返回的数据结构
        let resultsArray = [];
        
        // 判断返回数据的格式
        if (Array.isArray(result)) {
            // 如果直接返回数组（新格式）
            resultsArray = result;
        } else if (result && result.results) {
            // 兼容旧格式：如果包装在results字段中
            resultsArray = result.results;
        } else if (result && result.data && Array.isArray(result.data)) {
            // 兼容旧格式：如果包装在data字段中且是数组
            resultsArray = result.data;
        } else if (result && result.data && result.data.results) {
            // 兼容旧格式：如果嵌套在data.results字段中
            resultsArray = result.data.results;
        } else {
            console.error('批量请求返回无效数据，切换到单独请求模式');
            return individualRequests(requests, options);
        }
        
        // 3. 更新每个子请求的缓存
        for (let i = 0; i < requests.length && i < resultsArray.length; i++) {
            if (resultsArray[i] && resultsArray[i].success) {
                const req = requests[i];
                const data = resultsArray[i].data;
                
                // 更新单个请求的缓存
                if (req.cacheTime !== 0 && data !== null && data !== undefined) { // cacheTime=0 表示不缓存
                    setCache(req.route, req.params, data, req.cacheTime || DEFAULT_CACHE_TIME);
                }
            }
        }
        
        // 4. 返回结果数组
        return resultsArray.map(r => r.success ? r.data : null);
    } catch (error) {
        console.error('批量请求失败，切换到单独请求模式:', error);
        
        // 批量请求失败，禁用此功能，避免再次失败
        disableBatchRequest();
        
        // 回退到单独请求模式
        return individualRequests(requests, options);
    }
}

/**
 * 单独请求模式 - 当批量请求失败时的回退策略
 * @param {Array} requests 请求配置数组
 * @param {Object} options 请求选项
 */
async function individualRequests(requests, options = {}) {
    console.log('执行单独请求模式，共', requests.length, '个请求');
    
    // 修改每个请求的选项，避免显示多个加载提示
    const requestOptions = { ...options, hint: false };
    if (options.title) {
        // 只保留一个加载提示
        wx.showLoading({
            title: options.title || '加载中...',
            mask: true
        });
    }
    
    try {
        // 使用Promise.all并行请求数据
        const results = await Promise.all(
            requests.map(async (req) => {
                try {
                    const data = await requestData(
                        req.route,
                        req.params || {},
                        requestOptions,
                        req.cacheTime || DEFAULT_CACHE_TIME,
                        req.forceRefresh || false
                    );
                    return data;
                } catch (err) {
                    console.error('单独请求失败:', req.route, err);
                    return null;
                }
            })
        );
        
        return results;
    } catch (err) {
        console.error('单独请求模式失败:', err);
        return requests.map(() => null);
    } finally {
        if (options.title) {
            wx.hideLoading();
        }
    }
}

/**
 * 判断是否应该使用缓存
 * @param {String} route 请求路径
 * @param {Object} options 请求选项
 */
function shouldUseCache(route, options = {}) {
    // 如果明确指定不使用缓存，则返回false
    if (options.useCache === false) return false;
    
    // 默认GET请求可以使用缓存，其他请求方法不使用
    const method = (options.method || 'GET').toUpperCase();
    if (method !== 'GET') return false;
    
    // 包含'auth'、'login'、'logout'的路由不使用缓存
    if (/auth|login|logout|password/i.test(route)) return false;
    
    return true;
}

/**
 * 获取缓存时间
 * @param {String} route 请求路径
 * @param {Number} defaultTime 默认缓存时间
 */
function getCacheTime(route, defaultTime = DEFAULT_CACHE_TIME) {
    // 根据路由类型返回不同的缓存时间
    if (/config|setting/i.test(route)) {
        return 60 * 60 * 24; // 配置类数据缓存24小时
    }
    
    if (/user\/profile|user\/info/i.test(route)) {
        return 60 * 60; // 用户信息缓存1小时
    }
    
    if (/list|search/i.test(route)) {
        return 60 * 10; // 列表数据缓存10分钟
    }
    
    if (/statistics|count/i.test(route)) {
        return 60 * 15; // 统计数据缓存15分钟
    }
    
    return defaultTime;
}

/**
 * 智能处理API请求，支持缓存
 * @param {String} route 请求路径
 * @param {Object} params 请求参数
 * @param {Object} options 请求选项
 */
async function requestWithCache(route, params = {}, options = {}) {
    if (!route) {
        console.error('请求路径不能为空');
        return null;
    }
    
    // 是否使用缓存
    const useCache = shouldUseCache(route, options);
    const cacheTime = options.cacheTime || getCacheTime(route);
    
    // 如果可以使用缓存，先尝试从缓存获取
    if (useCache) {
        const cachedData = getCache(route, params);
        if (cachedData) {
            console.log(`[请求缓存] 命中: ${route}`);
            
            // 如果有静默刷新选项，则在后台刷新缓存
            if (options.silentRefresh) {
                setTimeout(() => {
                    // 异步静默更新缓存
                    wx.cloud.callFunction({
                        name: 'mcloud',
                        data: { route, params }
                    }).then(res => {
                        if (res.result && res.result.data) {
                            console.log(`[请求缓存] 静默更新: ${route}`);
                            setCache(route, params, res.result.data, cacheTime);
                        }
                    }).catch(err => {
                        console.error(`[请求缓存] 静默更新失败: ${route}`, err);
                    });
                }, 100);
            }
            
            return cachedData;
        }
    }
    
    // 没有命中缓存，发起实际请求
    try {
        const res = await wx.cloud.callFunction({
            name: 'mcloud',
            data: { route, params }
        });
        
        // 请求成功且有数据，缓存结果
        if (res.result && res.result.data && useCache) {
            console.log(`[请求缓存] 存储: ${route}`);
            setCache(route, params, res.result.data, cacheTime);
        }
        
        return res.result && res.result.data;
    } catch (err) {
        console.error(`[请求失败] ${route}:`, err);
        
        // 如果请求失败且缓存已过期但仍存在，使用过期缓存作为降级策略
        if (useCache && options.fallbackToExpiredCache) {
            // 尝试获取过期缓存
            const expiredKey = getCacheKey(route, params);
            try {
                const expiredData = wx.getStorageSync(expiredKey);
                if (expiredData && expiredData.data) {
                    console.log(`[请求缓存] 使用过期缓存作为降级: ${route}`);
                    return expiredData.data;
                }
            } catch (e) {
                console.error(`[请求缓存] 获取过期缓存失败: ${route}`, e);
            }
        }
        
        throw err;
    }
}

module.exports = {
    requestData,
    batchRequest,
    getCache,
    setCache,
    clearCacheByPrefix,
    clearAllCache,
    DEFAULT_CACHE_TIME,
    // 暴露批量请求开关，便于外部控制
    enableBatchRequest: () => { BATCH_REQUEST_ENABLED = true; },
    disableBatchRequest,
    shouldUseCache,
    getCacheTime,
    requestWithCache
}; 

